/*=============================================================================
*
*   Copyright (C) 2020 All rights reserved.
*
*   Filename:		crack.hpp
*
*   Author: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Date: 2020-03-20 16:16
*
*   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Last modified:	2020-03-30 22:23
*
*   Description:
*
=============================================================================*/
#ifndef CRACK_H
#define CRACK_H

#include "../geometry/mesh/mesh.hpp"
#include "tip_interface.hpp"
#include <boost/geometry/geometries/register/linestring.hpp>
#include <boost/geometry/geometries/register/multi_linestring.hpp>

namespace geoxfem {
namespace xfem {

template <typename DimensionType, typename degree>
struct Crack : geometry::Mesh<DimensionType, degree> {
  /*type info{{{*/
  using type           = boost::geometry::linestring_tag;
  using point_type     = typename traits::point_type<DimensionType>::type;
  using Shape          = geometry::ShapeInterface<DimensionType, degree>;
  using ptree          = boost::property_tree::ptree;
  using Mesh           = geometry::Mesh<DimensionType, degree>;
  using PointList      = typename Mesh::PointList;
  using iterator       = typename PointList::iterator;
  using const_iterator = typename PointList::const_iterator;
  using SELF           = xfem::Crack<DimensionType, degree>;
  using Tip            = TipInterface<SELF>;
  using TipList        = std::vector<std::unique_ptr<Tip>>;

  static constexpr size_t dimension = traits::dimension<Shape>::value;
  /*}}}*/
  Crack(const Mesh* mesh = nullptr) : Mesh{}, m_main_mesh{mesh}, m_list_tip{} {}

  void init() {
    std::unique_ptr<Tip> head_tip = std::make_unique<HeadTip<SELF>>(this);
    std::unique_ptr<Tip> tail_tip = std::make_unique<TailTip<SELF>>(this);
    m_list_tip.emplace_back(std::move(head_tip));
    m_list_tip.emplace_back(std::move(tail_tip));
    return;
  }

  /**
   * @brief addShape
   *
   * @param shp
   */
  void append(Shape* shp) final override { /*{{{*/
    /* 1. append shp to m_list_shape */
    auto& list = this->m_list_shape.at(shp->getTopologyDimension());
    auto  it   = std::find(list.begin(), list.end(), shp);
    if(it == list.end())
      list.emplace_back(shp);
    shp->clearGaussPoints();
    /* 2. insert afflicted points in order*/
    /* 2.1 if not segment, do nothing */
    if(!shp->ifSegment())
      return;
    /* 2.2 if first segment, insert head and tail points */
    point_type p0 = shp->getHeadPoint();
    point_type p1 = shp->getTailPoint();
    if(this->m_list_point.empty()) {
      appendBack(p0);
      appendBack(p1);
      return;
    }
    /* 2.3 * if not first segment */
    point_type cp0 = this->m_list_point.front();
    point_type cp1 = this->m_list_point.back();
    if(p0 == cp0) {
      appendFront(p1);
      boost::geometry::reverse(shp->toSegment());
      return;
    }
    if(p1 == cp0) {
      appendFront(p0);
      return;
    }
    if(p0 == cp1) {
      appendBack(p1);
      return;
    }
    if(p1 == cp1) {
      appendBack(p0);
      boost::geometry::reverse(shp->toSegment());
      return;
    }

    return;
  } /*}}}*/

  /*iterators for boost::geometry use{{{*/
  iterator begin() {
    return this->m_list_point.begin();
  }
  iterator end() {
    return this->m_list_point.end();
  }
  const_iterator begin() const {
    return this->m_list_point.begin();
  }
  const_iterator end() const {
    return this->m_list_point.end();
  }
  /*}}}*/
  Tip* getHeadTip() {
    return m_list_tip.front().get();
  }
  Tip* getTailTip() {
    return m_list_tip.back().get();
  }

private:
  void appendFront(point_type& point) {
    this->m_list_point.emplace_front(point);
    for(auto& tip : m_list_tip)
      tip->appendFront(point);
    return;
  }
  void appendBack(point_type& point) {
    this->m_list_point.emplace_back(point);
    for(auto& tip : m_list_tip)
      tip->appendBack(point);
    return;
  }

  const Mesh* m_main_mesh;
  TipList     m_list_tip;
};

}  // namespace xfem

}  // namespace geoxfem

GEOXFEM_REGISTER_TEMPLATIZED(geoxfem::xfem::Crack);

#endif
